#!/bin/bash
#
# MikoPBX - free phone system for small business
# Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
#

# Redirect output to the appropriate device based on serial port status
if test -w /dev/ttyS0 && ! /bin/busybox setserial -g /dev/ttyS0 | /bin/grep -q unknown; then
  exec </dev/console > >(/bin/busybox tee /dev/ttyS0) 2>&1
else
  exec </dev/console >/dev/console 2>/dev/console;
fi

# Calculate the MD5 hash of the buildtime file and save it in a variable
MD5_BUILD_INFO=$(/bin/busybox md5sum /etc/version.buildtime 2>/dev/null | /bin/busybox cut -d ' ' -f 1 2>/dev/null)

# Define the system PATH variable
PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH

liveCDDisk=""
liveCDPartition=""

# Import shell functions
. /sbin/shell_functions.sh

# Function to create boot configuration files
createBootConfFiles() {
  # Define the root directory for the user
  usrDir='/offload/rootfs/usr'
}

# Function to check the existence and integrity of the offload disk
checkOffloadDisk() {
  CHECK_RESULT=0  # Initialize the result variable

  # Define the path to the version file
  pathToFile="/offload/version.buildtime"

  # Check if the file exists
  if [ ! -f "${pathToFile}" ]; then
     # If it doesn't exist, set the result variable to 1 and print an error message
    echo " - File ${pathToFile} not found"
    CHECK_RESULT=1
  else
    # If it exists, calculate its MD5 hash and compare it with the stored MD5 hash
    md5OffloadBI=$(/bin/busybox md5sum $pathToFile 2>/dev/null | /bin/busybox cut -d ' ' -f 1 2>/dev/null)

    # If the hashes don't match, set the result variable to 2 and print an error message
    if [ ! "${md5OffloadBI}" = "${MD5_BUILD_INFO}" ]; then
      CHECK_RESULT=2
      echo " - Hash file ${pathToFile} verification failed"
    fi
  fi
}


#  This script is used to mount an offload disk. It will start by extracting the
#  UUID and filesystem type of the disk. After that, it will attempt to unmount
#  any previous mounts, then it checks the size of the disk. If the disk size is
#  less than or equal to 600, it runs a filesystem check. If the disk size is larger,
#  the function sets the MOUNT_RESULT to "cannot be offload, large size" and returns.
#
#  Afterwards, it will attempt to mount the disk, capture the return status in
#  MOUNT_RESULT, and then perform a check on the offload disk. If the check
#  doesn't pass, it will unmount the disk, set MOUNT_RESULT to the check's return
#  status, and pause for 1 second.
#
#  Finally, it calls the `createBootConfFiles` function.
#
#  Parameters:
#    disk: The disk to be mounted
#
#  Global Variables:
#    MOUNT_RESULT: Contains the status of the last mount operation
#
#  Returns:
#    No explicit return value. However, it may affect the global MOUNT_RESULT variable.
#
mountoffload() {
  # Disk variable which takes the first argument of the function
  disk="$1"

  # Get the UUID of the disk
  uuid=$(/sbin/blkid -ofull "$disk" | /bin/busybox sed -r 's/[[:alnum:]]+=/\n&/g' | /bin/busybox grep "^UUID" | /bin/busybox cut -d '"' -f 2 | /bin/busybox head -n 1)

   # Get the type of the filesystem of the disk
  type=$(/sbin/blkid -ofull "$disk" | /bin/busybox sed -r 's/[[:alnum:]]+=/\n&/g' | /bin/busybox grep "^TYPE=" | /bin/busybox cut -d '"' -f 2 | /bin/busybox head -n 1)

  # Unmount the /offload directory forcefully if it's already mounted
  umount -f /offload 2>/dev/null

  # Check the size of the disk.
  size_bytes="$(/bin/lsblk "$disk" -P -b -o SIZE | cut -f 2 -d '"')"

  # If the size of the disk is less than or equal to 600MB, it will perform a filesystem check
  if [ "$size_bytes" -le 629145600 ]; then
    echo " - Start check: $disk"
    "/sbin/fsck.${type}" -f -p "$disk" >/dev/null 2>/dev/null
  else
    MOUNT_RESULT='Disk size exceeds 600MB, cannot be offload partition'
    return
  fi

  # Mount the disk read-only using its UUID to /offload
  mount -t "${type}" -r UUID="$uuid" /offload 2>/dev/null

  # Save the exit status of the mount command to MOUNT_RESULT
  MOUNT_RESULT=$?

  # Check if the disk is properly mounted
  checkOffloadDisk

  # If the disk isn't properly mounted, unmount it and update MOUNT_RESULT with CHECK_RESULT
  if [ ! "${CHECK_RESULT}" = "0" ]; then
    umount -f /offload 2>/dev/null
    MOUNT_RESULT="${CHECK_RESULT}"
    sleep 1
  fi

  # Create configuration files for boot
  createBootConfFiles
}


# This script attempts to mount an existing configuration. It will create a
# directory for mounting, and then it will get a list of disks. For each disk,
# it checks whether it's already mounted or if it has a third partition. If not,
# it will skip to the next disk. If so, it will attempt a filesystem check and
# mount the disk read-only to the created directory. If the directory has a
# "conf" subdirectory and no "livecd" file, the script will exit. If not, it
# will unmount the directory and remove it before moving on to the next disk.
#
# Parameters:
#   offloaddisk: The disk to be mounted
#
# Returns:
#  0 if an existing configuration was found and mounted successfully, nothing otherwise.
mountConfRecover() {

  # Disk variable which takes the first argument of the function
  offloaddisk="$1"
  echo " - Attempting to mount existing configuration..."

   # Create a directory for mounting (old config)
  rec_dir='/conf.recover/'
  mkdir -p "${rec_dir}"

  # Get a list of disks
  diskrs=$(/bin/lsblk -b -r -o NAME,TYPE -n | /bin/busybox grep -e "disk$" -e 'raid' | /bin/busybox cut -d ' ' -f 1 | /bin/busybox sort -u)
  for diskr in $diskrs; do
    disk3=$(/bin/lsblk -r | /bin/busybox grep "$(/bin/busybox basename "$diskr")" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox grep "3$" | /bin/busybox sort -u)

     # Skip if the disk is already mounted
    if [ "${diskr}" = "${offloaddisk}" ]; then
      echo " - Skip ${diskr}, already mounted..."
      continue
    fi

    # Skip if the disk does not have a third partition
    if [ ! -b "/dev/${disk3}" ]; then
      echo " - Skip ${diskr}, part 3 not found..."
      continue
    fi

    # Get the type of the filesystem of the partition and run filesystem check if possible
    type=$(/sbin/blkid -o full "/dev/${disk3}" | /bin/busybox sed -r 's/[[:alnum:]]+=/\n&/g' | /bin/busybox grep "^TYPE" | /bin/busybox cut -d '"' -f 2 | /bin/busybox head -n 1)
    if [ -f "/sbin/fsck.${type}" ]; then
      "/sbin/fsck.${type}" -f -p "/dev/${disk3}" >/dev/null 2>/dev/null
      echo " - Trying fsck /dev/${disk3} ... returned: $?"
    fi

    # Get the UUID of the partition and attempt to mount it read-only to the created directory
    uuid=$(/sbin/blkid -o full "/dev/${disk3}" | /bin/busybox sed -r 's/[[:alnum:]]+=/\n&/g' | /bin/busybox grep "^UUID" | /bin/busybox cut -d '"' -f 2 | /bin/busybox head -n 1)
    mount -t "${type}" -r UUID="${uuid}" "${rec_dir}"
    echo " - Trying mount ${diskr} fs ${type} ... returned: $?"

     # If the directory has a "conf" subdirectory and no "livecd" file, exit
    if [ -d "${rec_dir}conf" ] && [ ! -f "${rec_dir}livecd" ]; then
      echo " - Found existing configuration on ${disk3}!"
      exit 0
    else
      # If not, unmount the directory and remove it before moving on to the next disk
      umount -f "${rec_dir}" >/dev/null 2>/dev/null
      rm -rf "${rec_dir}"
    fi
  done

}

# Clear the freeoffload temporary directory if it exists
rm -rf /tmp/freeoffload

################################################################################
# LIVE CD-ROM
################################################################################

# Start of the block for LIVE CD-ROM
echo " - Waiting for device ready..."
busybox sleep 4

# Attempt to identify CD-ROM drives
cddev=$(/bin/lsblk -b -d -r -o NAME,TYPE -n | /bin/busybox grep "rom$" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox uniq)
length=0

# Attempt to identify device by UUID
/sbin/blkid "/dev/${cddev}" >/dev/null 2>&1
blkResult="$?"

# If a CD-ROM drive is found, update length with its identifier length
if [ "$blkResult" -eq 0 ]; then
  length=${#cddev}
fi
echo " - Searching for RootFS on LiveCD..."

# If the length of the identifier is more than 2, it means we have identified a CD-ROM drive,
# and we should use this for subsequent operations. Otherwise, try to identify regular disks.
if [ "$length" -gt 2 ]; then
  disks=${cddev}
else
  disks=$(/bin/lsblk -b -d -r -o NAME,TYPE -n | /bin/busybox grep "disk$" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox uniq)
fi

# For each identified disk (or the CD-ROM drive), attempt to mount it as an offload disk.
for disk in $disks; do
  if [ "$length" -gt 2 ]; then
    diskPath="/dev/${disk}"
  else
    partitionName=$(/bin/lsblk -r | /bin/busybox grep "$(/bin/busybox basename "$disk")" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox grep "1$")
    diskPath="/dev/${partitionName}"
  fi

  # Check if the disk or partition exists
  if [ ! -b "$diskPath" ]; then
    continue
  fi

  # Attempt to mount the disk or partition as an offload disk
  echo
  echo " - Mounting $diskPath..."
  mountoffload "$diskPath"
  echo " - Mount result: $MOUNT_RESULT"

  # Check for the presence of a "livecd" flag file in the root of the offload disk.
  if [ -f /offload/livecd ]; then
    echo " - Found LiveCD image!"
    echo "  Press any key within 30 seconds to boot from LiveCD..."
    read -t 30 -n 1 input
    if [ $? = 0 ]; then
       echo " - User selected to boot from LiveCD."
       # Call function to create boot configuration files and recover configuration from the offload disk
       createBootConfFiles
       mountConfRecover "$disk"
       # Exit after successfully mounting the offload disk
       exit 0
    else
        liveCDDisk=$disk
        liveCDPartition=$diskPath
    fi
  fi

  # If the "livecd" flag file was not found, unmount the offload disk and continue with the next one
  umount -f /offload 2>/dev/null
done

################################################################################
# Start of the block for RAID disks
################################################################################

echo
echo " - Searching for RootFS on RAID devices..."

# Identify RAID drives
disks=$(/bin/lsblk -b -r -o NAME,TYPE -n | /bin/busybox grep "raid" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox sort -u)
for disk in $disks; do

  # Get the second partition from the RAID disk
  partitionName=$(/bin/lsblk -r | /bin/busybox grep "$(/bin/busybox basename "$disk")" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox grep "2$" | /bin/busybox sort -u)
  diskPath="/dev/${partitionName}"

   # Check if the partition exists
  if [ ! -b "$diskPath" ]; then
    continue
  fi

  # Attempt to mount the partition as an offload disk
  echo
  echo " - Mounting $diskPath..."
  mountoffload "$diskPath"
  echo " - Mount result: $MOUNT_RESULT"

   # Check for the presence of a "rootfs" directory in the root of the offload disk
  if [ -d /offload/rootfs ]; then
    echo " - RootFS founded on $diskPath!"
    exit 0
  fi

   # If the "rootfs" directory was not found, unmount the offload disk and continue with the next one
  umount -f /offload >/dev/null
  echo " - Failed to find RootFS on $diskPath!"
done

################################################################################
# Start of the block for Hard Disk Drives (HDDs)
################################################################################

echo
echo " - Searching for RootFS on block devices..."

# Identify HDDs
disks=$(/bin/lsblk -b -d -r -o NAME,TYPE -n | /bin/busybox grep "disk$" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox uniq)

# Iterate over each identified disk
for disk in $disks; do

   # Get the second partition of the HDD
  partitionName=$(/bin/lsblk -r | /bin/busybox grep "$(/bin/busybox basename "$disk")" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox grep "2$")

  # Construct the path of the disk partition
  diskPath="/dev/${partitionName}"

  # Check if the partition exists
  if [ ! -b "$diskPath" ]; then
    continue
  fi

   # Attempt to mount the partition as an offload disk
  echo
  echo " - Mounting $diskPath..."
  mountoffload "$diskPath"
  echo " - Mount result: $MOUNT_RESULT"

  # Check if a directory named "rootfs" exists in the root of the offload disk
  if [ -d /offload/rootfs ]; then
    echo " - RootFS founded on $diskPath!"
    exit 0
  fi

   # If the "rootfs" directory was not found, unmount the offload disk and log failure to find "rootfs"
  umount -f /offload >/dev/null
  echo " - Failed to find RootFS on $diskPath!"
done

################################################################################
# Start of the block for Universal Serial Bus (USB) devices
################################################################################

echo
echo " - Waiting for disks USB ready and trying again..."

# Sleep for 10 seconds to give disks time to settle
/bin/busybox sleep 10

echo " - Searching for RootFS on USB devices..."

# Identify USB devices
disks=$(/bin/lsblk -b -d -r -o NAME,TYPE -n | /bin/busybox grep "disk$" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox uniq)

# Iterate over each identified disk
for disk in $disks; do

   # Get the first partition of the USB device
  partitionName=$(/bin/lsblk -r | /bin/busybox grep "$(/bin/busybox basename "$disk")" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox grep "1$")

  # Construct the path of the disk partition
  diskPath="/dev/${partitionName}"

  # Check if the partition exists
  if [ ! -b "$diskPath" ]; then
    continue
  fi

  # Attempt to mount the partition as an offload disk
  echo
  echo " - Mounting $diskPath..."
  mountoffload "$diskPath"
  echo " - Mount result: $MOUNT_RESULT"

  # Check if a file named "livecd" exists in the root of the offload disk
  if [ -f /offload/livecd ]; then
    echo " - Found LiveUSB image..."
    mountConfRecover "$disk"
    exit 0
  fi

   # If the "livecd" file was not found, unmount the offload disk
  umount -f /offload
done

# Checking for a rootfs directory on the second partition of each USB device
echo
for disk in $disks; do

  # Get the second partition of the USB device
  partitionName=$(/bin/lsblk -r | /bin/busybox grep "$(/bin/busybox basename "$disk")" | /bin/busybox cut -d ' ' -f 1 | /bin/busybox grep "2$")

  # Construct the path of the disk partition
  diskPath="/dev/${partitionName}"

  # Check if the partition exists
  if [ ! -b "$diskPath" ]; then
    continue
  fi

  # Attempt to mount the partition as an offload disk
  echo
  echo " - Mounting $diskPath..."
  mountoffload "$diskPath"
  echo " - Mount result: $MOUNT_RESULT"

  # Check if a directory named "rootfs" exists in the root of the offload disk
  if [ -d /offload/rootfs ]; then
    echo " - RootFS founded on $diskPath!"
    exit 0
  fi
  echo " - Failed to find RootFS on $diskPath!"
done

# Default action - boot from LiveCD if it was found
if [ -n "$liveCDPartition" ]; then
    mountoffload "$liveCDPartition"
    createBootConfFiles
    mountConfRecover "$liveCDDisk"
    exit 0
fi

# Log message indicating failure to find the /offload partition
echo "The system was unable to find the /offload partition!"
echo
echo "Please send the output of 'ls /dev' and 'fdisk -l' to the"
echo "development team for assistance."
echo "Apologies for the inconvenience, and thank you for your patience as we resolve this issue."
echo

exit 1